/**
 *
 */
package com.bardsoftware.foronuvolo.sfimport;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;

import com.bardsoftware.foronuvolo.data.Discussion;
import com.bardsoftware.foronuvolo.data.ForumUser;
import com.bardsoftware.foronuvolo.data.Message;
import com.bardsoftware.foronuvolo.data.PMF;
import com.google.appengine.repackaged.com.google.common.collect.Lists;
import com.google.appengine.repackaged.com.google.common.collect.Maps;
import com.google.appengine.repackaged.com.google.common.collect.Sets;

class ParsingState {
    private static final Set<Long> ourVisitedDiscussions = Sets.newHashSet();
    private static final Logger LOGGER = Logger.getLogger(SFImportServlet.class.getName());
    private final HashMap<Long, List<Message>> myDiscussions = Maps.newHashMap();
    private final int myStart;
    private final int myLimit;
    private Message myCurrentMessage;

    private Date myCurrentDate;
    private String myCurrentText;
    private Long myDiscussionID;
    private String myUserID;
    private String myUserDisplayName;
    private String myUrl;
    private String mySubject;
    private boolean isInitialMessage;
    private Long myMessageID;

    private int myNewDiscussionCount;
    private int myUpdatedDiscussionCount;
    private int myUntouchedDiscussionCount;
    private int myMessageCount;
    private final boolean isVerification;

    ParsingState(int start, int limit, boolean verification) {
        myStart = start;
        myLimit = limit;
        isVerification = verification;
    }

    public void buildMessage() {
        if (!isReady()) {
            myMessageCount++;
            return;
        }
        myMessageCount++;
        try {
            doBuildMessage();
        }
        finally {
            myCurrentDate = null;
            myCurrentText = null;
            myDiscussionID = null;
            myUrl = null;
            mySubject = null;
            myUserDisplayName = null;
            myUserID = null;
            isInitialMessage = false;
            myMessageID = null;
        }
    }

    private void doBuildMessage() {
        if (isVerification) {
            PMF.runInTransaction(null, new Runnable() {
                public void run() {
                    if (Message.findBySourceForgeID(myMessageID) == null) {
                        LOGGER.severe("message=" + myMessageID +" not found");
                    }
                }
            }, 3);
            return;
        }
        if (myCurrentDate == null) {
            return;
        }
        LOGGER.fine("Start building message="+myMessageID);
        long start = System.currentTimeMillis();
        ForumUser forumUser;
        if (myUserID == null || "".equals(myUserID)) {
            forumUser = ForumUser.ANONYMOUS;
            LOGGER.warning("userID=" + myUserID + " for message=" + myMessageID);
        } else {
            forumUser = ForumUser.find(myUserID);
            if (forumUser == null) {
                forumUser = new ForumUser(myUserID, myUserDisplayName);
                PMF.runInTransaction(forumUser, PMF.JUST_PERSIST, 3);
            }
        }
        myCurrentMessage = new Message(null, forumUser, "= " + mySubject+" =\n" + myCurrentText, myCurrentDate);
        myCurrentMessage.setSourceUrl(myUrl);
        myCurrentMessage.setSourceForgeID(myMessageID);
        List<Message> discussion = myDiscussions.get(myDiscussionID);
        if (discussion == null) {
            discussion = Lists.newArrayList();
            myDiscussions.put(myDiscussionID, discussion);
            LOGGER.fine("Created new message list for discussion=" + myDiscussionID);
        }
        discussion.add(0, myCurrentMessage);
        if (isInitialMessage) {
            if (ourVisitedDiscussions.contains(myDiscussionID)) {
                LOGGER.severe("We already have discussion="+myDiscussionID);
            }
            saveDiscussion(discussion, myDiscussionID);
            myDiscussions.remove(myDiscussionID);
            LOGGER.fine("removing discussion="+myDiscussionID+" from cache");
            ourVisitedDiscussions.add(myDiscussionID);
        }
        LOGGER.fine("building message=" + myMessageID + "took "+(System.currentTimeMillis() - start) + "ms");
    }
    private void saveDiscussion(final List<Message> discussion, final Long discussionID) {
        if (isVerification) {
            return;
        }
        final PersistenceManager pm = PMF.getFactory().getPersistenceManager();
        PMF.runInTransaction(pm, null, new Runnable() {
            public void run() {
                long start = System.currentTimeMillis();
                LOGGER.fine("saving discussion=" + discussionID);
                Discussion d = Discussion.findBySourceForgeID(pm, discussionID);
                d = doSaveDiscussion(d, discussion, discussionID);
                pm.makePersistent(d);
                LOGGER.fine("took " + (System.currentTimeMillis() - start) + "ms");
            }
        }, 3);
    }
    private Discussion doSaveDiscussion(Discussion d, List<Message> discussion, Long discussionID) {
      if (d == null) {
          LOGGER.fine("Discussion=" + discussionID+" not found. Creating new one");
          d = new Discussion(discussion);
          d.setSourceForgeID(discussionID);
          myNewDiscussionCount++;
      } else {
          Set<Long> existingMessages = Sets.newHashSet();
          for (Message m: d.getSortedMessages()) {
              existingMessages.add(m.getSourceForgeID());
          }
          LOGGER.fine("Discussion="+discussionID+" found. Messages:\n" + existingMessages);
          List<Message> newMessages = Lists.newArrayListWithExpectedSize(discussion.size());
          for (Message m: discussion) {
              if (!existingMessages.contains(m.getSourceForgeID())) {
                  newMessages.add(new Message(null, m));
              }
          }
          if (!newMessages.isEmpty()) {
              myUpdatedDiscussionCount++;
              LOGGER.fine("adding #messages:"+newMessages.size());
              d.addAllMessages(newMessages);
              LOGGER.fine("now messages are:");
              for (Message m: d.getSortedMessages()) {
                  LOGGER.fine(""+m.getSourceForgeID());
              }
          }
          else {
              myUntouchedDiscussionCount++;
          }

      }
      return d;
    }

    public void setText(String s) {
        myCurrentText = s;
    }
    public void setDate(Date date) {
        myCurrentDate = date;
    }
    public void setMessageID(Long id) {
        myMessageID = id;
    }
    public void setDiscussionID(Long id) {
        myDiscussionID = id;
    }
    public void setUserID(String s) {
        myUserID = s;
    }
    public void setUserDisplayName(String s) {
        myUserDisplayName = s;
    }
    public void setUrl(String s) {
        myUrl = s;
    }
    public void setSubject(String s) {
        mySubject = s;
    }
    public void setInitialMessage(boolean b) {
        isInitialMessage = b;
    }
    public boolean isReady() {
        return (myMessageCount >= myStart) && (myMessageCount < myLimit);
    }
    public void saveRemainingDiscussions() {
        LOGGER.info("saving remaining discussions");
        for (Map.Entry<Long, List<Message>> remaining : myDiscussions.entrySet()) {
            saveDiscussion(remaining.getValue(), remaining.getKey());
        }
    }

    public int getMessageCount() {
        return myMessageCount;
    }

    public String getStats() {
        return "new: " + myNewDiscussionCount
               + "\nupdated:" + myUpdatedDiscussionCount
               + "\nuntouched:" + myUntouchedDiscussionCount
               + "\nmessages:" + myMessageCount + "\n";
    }

    public void setLogLevel(Level l) {
        LOGGER.setLevel(l);
    }
}